package com.tjetc;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.tjetc.annotation.Controller;
import com.tjetc.annotation.RequestMapping;
import com.tjetc.annotation.RequestParam;
import com.tjetc.annotation.ResponseBody;
import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;

/**
 * 中央控制器
 *
 * @author gaoyongxiang
 * @version v1.0
 * @date 2025/9/8
 */
public class MyDispatchServlet extends HttpServlet {

    // 保存ioc容器
    public MyWebApplicationContext webApplicationContext;

    // 保存所有的映射信息 省略拦截器部分 及其余部分
    // 真正获取映射器应该从我们的ioc容器中 获取 这里直接简化 放到一个list即可
    List<MyHandler> handlerList = new ArrayList<>();


    // 这个是Servlet的生命周期
    @Override
    public void init(ServletConfig config) throws ServletException {
//        System.out.println("执行初始化" + config.getInitParameter("contextConfigLocation"));
        // 在初始化的过程中 我们需要将SpringMVC容器构建好
        webApplicationContext = new MyWebApplicationContext(config.getInitParameter("contextConfigLocation"));

        // 得到所有的映射器
        getHandlerMapping();

//        System.out.println("handlerList = " + handlerList);
    }


    // 得到所有的映射器
    private void getHandlerMapping() {
        webApplicationContext.getIoc().forEach((k, v) -> {
            // k:beanName v：对应的实体类对象
            // 得到所有注入bean对象的clazz对象
            Class<?> clazz = v.getClass();
            // 判断当前类上的注解是不是controller注解
            if (clazz.isAnnotationPresent(Controller.class)) {
                // 获取当前controller中所有的方法
                Method[] declaredMethods = clazz.getDeclaredMethods();
                // 遍历方法啊
                for (Method declaredMethod : declaredMethods) {
                    // 因为只有requestMapping 才会进行访问
                    if (declaredMethod.isAnnotationPresent(RequestMapping.class)) {
                        // 获取requestMapping中的value值
                        RequestMapping requestMapping = declaredMethod.getAnnotation(RequestMapping.class);
                        // 我们真正请求的地址应该是 项目的工程目录 + url 如果不加项目的根目录 那么tomcat的配置 就必须是/
//                        System.out.println("getServletContext().getContextPath() = " + getServletContext());
                        String url = requestMapping.value();
                        MyHandler handler = new MyHandler(url, v, declaredMethod);
                        handlerList.add(handler);
                    }
                }
            }
        });
    }


    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 省略request参数处理
        // 实现分发
        doDispatch(req, resp);
    }

    // 实现请求的分发
    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) {
        // 这里我们省略了适配器 我们直接从映射中拿到controller即可
        // 根据请求对象 拿到对应的请求路径并找到对应的处理器
        MyHandler handler = getHandler(req);
        try {
            if (Objects.isNull(handler)) {
                resp.getWriter().write("<h1>404 NOT FOUND</h1>");
            } else {
//                System.out.println("开始处理");

                // 此时我们需要封装一个参数数组进行传递
                // 我们要先拿到当前方法中的参数类型
                Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();

                // 所以我们将来传递的参数数组的长度要和获取出来类型参数数组的长度一致
                Object[] params = new Object[parameterTypes.length];
//                params[0] = req;
//                params[1] = resp;

                // 遍历形参类型数组，根据参数类型信息，找到对应的实参并进行填充
                // 这里我们要使用fori进行遍历 因为我们知道参数类型的位置
                for (int i = 0; i < parameterTypes.length; i++) {
                    // 取出每一个参数类型
                    Class<?> parameterType = parameterTypes[i];
                    // parameterType: java.lang.String
                    if (Objects.equals("HttpServletRequest", parameterType.getTypeName())) {
                        params[i] = req;
                    } else if (Objects.equals("HttpServletResponse", parameterType.getTypeName())) {
                        params[i] = resp;
                    }
                }

                // 因为我们将来方法中 不可能仅仅只有req，resp 还要有String name 所以我们还需要处理除了
                // req, resp 之外的其他参数 进行填充到参数数组中
                // 从请求中获取所有参数
                // 比如： http://localhost:8080/name=zhangsan
                // 防止出现request的乱码问题
                req.setCharacterEncoding("utf-8");
                Map<String, String[]> parameterMap = req.getParameterMap();

                // 遍历所有从请求中获取到的参数列表
                parameterMap.forEach((k, v) -> {
                    // k: 参数名 name
                    // v: 参数值 zhangsan
                    // 简化处理：只处理单一值 不处理多个参数情况
                    String paramValue = v[0];

                    // 开始填充到参数数组中 填充的问题是什么：
                    // 从路径中获取的name参数 怎么和方法中的参数对应！！！！！
                    // 封装一个 通过请求的名称 找到该参数在方法中的参数列表中的位置
                    int index = getIndexRequestIndex(handler.getMethod(), k);
                    // 
                    if (!Objects.equals(-1, index)) {
                        params[index] = paramValue;
                    } else {
                        // 如果没有找到@RequestParam 注解的参数 ，但是存在没有RequestParam修饰的参数
                        // 我们要根据对应的名称映射自动注入
                        // 封装一个方法 获取所有该方法中的参数名称
                        List<String> parameterNames = getParameterNames(handler.getMethod());
                        // 遍历所有名称 进行填充
                        for (int i = 0; i < parameterNames.size(); i++) {
                            if (Objects.equals(k, parameterNames.get(i))) {
                                params[i] = paramValue;
                                break;
                            }
                        }
                    }
                });

                // todo: 当前方法的问题 参数固定 要执行的方法 必须有req和resp两个 否则不执行
                // 解决：变成一个可变参数
//                handler.getMethod().invoke(handler.getController(), req, resp);
//                handler.getMethod().invoke(handler.getController(), params);
                Object result = handler.getMethod().invoke(handler.getController(), params);

                // 这里应该将result交给视图解析器来形成最后的view视图
                // 简化处理： 直接让本类 来执行了
                if (result instanceof String) {
                    // 如果是String类型 也有可能是返回json数据 这里简化 只认为返回的是视图
                    // viewName：
                    /*
                        三种情况：
                        （1） 返回的就是ok.jsp
                        （2） forward:ok.jsp
                        (3)  redirect:ok.jsp
                     */
                    String viewName = (String) result;
                    if (viewName.contains(":")) {
                        // 先取出viewName的前缀
                        // 跳转类型
                        String viewType = viewName.split(":")[0];
                        // 要跳转的页面地址
                        String viewPage = viewName.split(":")[1];
                        // 判断是转发还是重定向
                        if (Objects.equals(viewType, "forward")) {
                            req.getRequestDispatcher(viewPage).forward(req, resp);
                        } else if (Objects.equals(viewType, "redirect")) {
                            resp.sendRedirect(viewPage);
                        }
                    } else {
                       req.getRequestDispatcher(viewName).forward(req, resp);
                    }
                }
                // 处理json的情况 简化操作 只处理List
                else if (result instanceof ArrayList) {
                    Method method = handler.getMethod();
                    // 判断该方法是否有@ResponseBody注解
                    if (method.isAnnotationPresent(ResponseBody.class)) {
                        // 借助json处理工具
                        ObjectMapper objectMapper = new ObjectMapper();
                        String jsonData = objectMapper.writeValueAsString(result);

                        // 防止乱码 设置响应头
                        resp.setContentType("text/html;charset=utf-8");
                        PrintWriter writer = resp.getWriter();
                        writer.write(jsonData);
//                        writer.close();
                    }
                }


            }
        } catch (Exception e) {

        }

    }

    // 获取所有该方法中的参数名称
    private List<String> getParameterNames(Method method) {
        List<String> parameterNames = new ArrayList<>();
        Parameter[] parameters = method.getParameters();
        // 这里会有一个问题  返回的名称是一个arg0
        for (Parameter parameter : parameters) {
//            System.out.println("parameter.getName() = " + parameter.getName());
            parameterNames.add(parameter.getName());
        }
        return parameterNames;
    }

    // 通过请求的名称 找到该参数在方法中的参数列表中的位置
    private int getIndexRequestIndex(Method method, String k) {
        // 获取方法中所有的参数名称
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            // @RequestParam
            if (parameter.isAnnotationPresent(RequestParam.class)) {
                // 如果存在获取该注解中的值
                RequestParam requestParam = parameter.getAnnotation(RequestParam.class);
                if (Objects.equals(k, requestParam.value())) {
                    // 如果匹配到了 直接返回下标即可
                    return i;
                }
            }
        }
        // 说明没有找到 返回-1
        return -1;
    }

    // 根据请求对象 拿到对应的请求路径并找到对应的处理器
    private MyHandler getHandler(HttpServletRequest req) {
        // 访问路径：http://localhost:8080/f
        // url 和 uri
        // url：http://localhost:8080/f
        // uri: /f
        // 遍历映射器集合 找到对应的映射器
        for (MyHandler handler : handlerList) {
            if (Objects.equals(handler.getUrl(), req.getRequestURI())) {
                return handler;
            }
        }

        return null;

    }
}